package com.yibei.currentutils.annotation;

import java.lang.reflect.Method;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import com.google.common.util.concurrent.RateLimiter;
import com.yibei.currentutils.exception.CurrentLimitException;
import com.yibei.currentutils.model.BucketDTO;
import com.yibei.currentutils.utils.BucketLimitManage;

import lombok.extern.slf4j.Slf4j;

@Aspect
@Component
@Order(-1)
@Slf4j
public class CurrentLimitAspect {
    /**
     * *此处的切点是注解的方式，也可以用包名的方式达到相同的效果
     * 
     */
    @Pointcut("@annotation(com.yibei.currentutils.annotation.CurrentLimit)")
    public void operation() {
    }

    /**
     * 环绕通知
     * */
    @Around("operation()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        Class<?> targetCls = jp.getTarget().getClass();//类对象
        MethodSignature ms = (MethodSignature) jp.getSignature();
        Class<?>[] paramTypes = ms.getMethod().getParameterTypes();//方法参数
        Method tarMethod = targetCls.getDeclaredMethod(ms.getName(), paramTypes);//方法对象
        CurrentLimit annotation = tarMethod.getAnnotation(CurrentLimit.class);
        String switc = annotation.switc();
        long refreshInterval = annotation.refreshInterval();
        Integer capacity = annotation.capacity();
        Integer allowQuota = annotation.allowQuota();
        Integer perSecond = annotation.perSecond();
        boolean isopen = annotation.isopen();
        BucketDTO bucketDTO = new BucketDTO(capacity, allowQuota, perSecond, isopen);
        if (!getAccess(switc, refreshInterval, bucketDTO)) {
            throw new CurrentLimitException("NOAccess");
        }
//        System.out.println("进入切面");
        return jp.proceed();
    }

    private BucketDTO bucketDTO_bucket;

    private BucketDTO bucketDTO_token;

    private static long time = System.currentTimeMillis();

    private static RateLimiter limiter = RateLimiter.create(50);

    private static BucketLimitManage bucket = new BucketLimitManage();

    /**
     * 
     *
     * 参数: @param switc:选择算法
     * 参数: @param interval:获取配置时间间隔
     * 参数: @return
     * 返回类型： boolean
     * @exception
     * @since  1.0.0
     */
    private boolean getAccess(String switc, long interval, BucketDTO bucketDTO) {
        long now = System.currentTimeMillis();
        if (now - time > interval) {
            bucketDTO_bucket = null;
            bucketDTO_token = null;
            time = now;
        }
        if ("bucket".equals(switc)) {
            log.info("使用漏桶算法限流！");
            if (bucketDTO_bucket == null) {
                bucketDTO_bucket = bucketDTO;
                bucket = new BucketLimitManage();
                log.info(String.format(
                        "桶子大小：%s，流速：%s每秒，允许访问！",
                        bucketDTO_bucket.getCapacity(),
                        bucketDTO_bucket.getAllowQuota() / bucketDTO_bucket.getPerSecond()));

            }
            if (!bucketDTO_bucket.isIsopen()) {
                log.info("限流关闭！");
                return true;
            }
            int capacity = bucketDTO_bucket.getCapacity();//容量
            int allowQuota = bucketDTO_bucket.getAllowQuota();//单位时间允许请求数
            int persecond = bucketDTO_bucket.getPerSecond();//单位时间
            return bucket.isActionAllowed(capacity, allowQuota, persecond);
        } else if ("token".equals(switc)) {
            log.info("使用令牌算法！");
            if (bucketDTO_token == null) {
                bucketDTO_token = bucketDTO;
                int tokenAmount = bucketDTO_token.getCapacity();
                limiter = RateLimiter.create(tokenAmount);
            }
            if (!bucketDTO_token.isIsopen()) {
                log.info("限流关闭！");
                return true;
            }
            return BucketLimitManage.exec(limiter);
        }
        return true;
    }

}
